#!/usr/bin/env bash

#####################################################################
# File Name: install_minio_cluster.sh
# Version: V1.0
# Author: Brian Hsiung
# Created Time : 2025-01-23 10:24:53
# Description: 部署4节点Minio集群
####################################################################

# 调试跟踪（-e: 与错退出， -u: 捕获未定义的变量， -x: 调试模式，在执行每个命令前，将其打印到标准错误输出）
# set -eux

# 运行用户
declare -r MINIO_USER="minio"
# 数据存储目录，只能使用xfs文件系统
declare -r MINIO_DATA="/data/minio"
# 账号
declare -r MINIO_ACCOUNT="admin"
# 密码
declare -r MINIO_PASSWD="ZB9i!rLNSCjz@dnnR"
# API端口
declare -r MINIO_API_PORT="9000"
# 控制台端口
declare -r MINIO_CONSOLE_PORT="9001"
# 阿里云OSS存储地址
declare -r OSS_BASE_URL="https://brianhsiung.oss-cn-hangzhou.aliyuncs.com"

WORK_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$WORK_DIR" || exit

### 日志函数
log_info()    { echo -e "\033[34m[INFO] $*\033[0m"; }
log_warn()    { echo -e "\033[33m[WARNING] $*\033[0m"; }
log_success() { echo -e "\033[32m[SUCCESS] $*\033[0m"; }
log_error()   { echo -e "\033[31m[ERROR] $*\033[0m"; exit 1; }

### 检查依赖
check_dependency() {
    local cmd=$1
    if ! command -v "$cmd" &>/dev/null; then
        log_error "依赖缺失: 请先安装 $cmd"
    fi
}

### 检查端口
check_ports() {
    local port=$1
    if ss -lntu | grep -q ":$port " >/dev/null; then
        log_error "端口 $port 被占用"
    fi
}

#############################################
# 设置目录权限（递归检查上级目录）
#############################################
set_parent_permissions() {
    ### 某些等保三服务器默认新建的的目录权限为700，文件权限为600，导致服务无法启动，所以需要遍历上级目录，将权限修改为755
    # 获取真实目录
    local target_dir
    target_dir=$(realpath "$1")
    # 获取上级目录
    local current_dir
    current_dir=$(dirname "$target_dir")

    # 做个判断，避免进入死循环
    [ -d "$target_dir" ] || log_error "目录不存在 [$target_dir]"

    # 排除敏感目录
    local -a exclude_dirs
    exclude_dirs=("/usr" "/usr/local" "/bin" "/sbin" "/lib" "/etc" "/dev" "/var" "/root" "/usr/share")

    while [[ "$current_dir" != "/" ]]; do

        # 排除敏感目录
        if printf '%s\n' "${exclude_dirs[@]}" | grep -qx "$current_dir"; then
            log_info "跳过系统保护目录: $current_dir"
            current_dir=$(dirname "$current_dir")
            continue
        fi

        # 跳过不存在的目录
        if [[ ! -d "$current_dir" ]]; then
            log_warn "警告: 目录不存在 [$current_dir]"
            current_dir=$(dirname "$current_dir")
            continue
        fi
        
        # 获取当前目录权限
        local perm_oct
        perm_oct=$(stat -c '%a' "$current_dir")
        log_info "目录 $current_dir 的原始权限为: $perm_oct"
        
        if [[ "$perm_oct" != "755" ]]; then
            log_info "正在设置权限: [755] -> $current_dir"
            if ! chmod 755 "$current_dir"; then
                log_error "错误: 无法设置目录权限 [$current_dir]"
            fi
        else
            log_success "权限正确 [755]: $current_dir"
        fi

        # 向上一级目录
        current_dir=$(dirname "$current_dir")
    done
}

generate_init_env () {

    [[ "$(id -u)" -ne 0 ]] && log_error "请使用root账号执行此脚本！"

    check_ports "$MINIO_API_PORT"
    check_ports "$MINIO_CONSOLE_PORT"

    if [ ! -d "$MINIO_DATA" ]; then
        mkdir -p "$MINIO_DATA"
    elif [ "$(ls -A "$MINIO_DATA")" ]; then
        log_error "目录 $MINIO_DATA 已存在且不为空"
    fi

    local filetype
    filetype=$(df -T "$MINIO_DATA"|awk 'NR==2{print $2}')
    if [ "$filetype" != "xfs" ]; then
        log_error "当前文件系统格式为 $filetype ,请使用xfs文件系统"
    fi

    local arch
    local cpu_type
    arch=$(uname -m)

    case "${arch}" in
        amd64|x86_64)
            cpu_type="amd64" ;;
        arm64|aarch64)
            cpu_type="arm64" ;;
        *)
            echo "不支持的CPU架构"
            exit 1 ;;
    esac

    if curl -sIfo /dev/null --connect-timeout 5 --max-time 10 "$OSS_BASE_URL"/health; then
        if [[ ! -f "minio" ]]; then
            log_info "使用在线安装模式"
            check_dependency "wget"
            log_info "从阿里云OSS $OSS_BASE_URL/deploy/minio/$cpu_type/minio 下载minio"
            wget -q "$OSS_BASE_URL/deploy/minio/$cpu_type/minio" || log_error "下载Minio失败"
        fi
    else
        log_info "使用离线安装模式"
    fi

    [ -f minio ] || log_error "当前目录无 minio"

    [ -f "/usr/local/bin/minio" ] || { /bin/cp -f minio /usr/local/bin/minio || log_error "移动 minio 失败";}

    chmod 755 /usr/local/bin/minio

    if systemctl is-active --quiet firewalld; then
        log_info "防火墙已开启，将开放端口 $MINIO_API_PORT 和 $MINIO_CONSOLE_PORT"
        firewall-cmd --permanent --zone=public --add-port="$MINIO_API_PORT"/tcp
        firewall-cmd --permanent --zone=public --add-port="$MINIO_CONSOLE_PORT"/tcp
        firewall-cmd --reload
    fi

    if ! getent group "$MINIO_USER" >/dev/null 2>&1; then
        log_info "用户组 $MINIO_USER 不存在，将创建用户组"
        groupadd -r "$MINIO_USER"
    fi

    if ! getent passwd "$MINIO_USER" >/dev/null 2>&1; then
        log_info "用户 $MINIO_USER 不存在，将创建用户"
        useradd -M -r -g "$MINIO_USER" "$MINIO_USER" -s /sbin/nologin
    fi

    set_parent_permissions "$MINIO_DATA"
    chown "$MINIO_USER":"$MINIO_USER" -R "$MINIO_DATA"
    
}

# 生成MinIO配置文件
generate_minio_volumes () {
    # 官方推荐的格式是这样的(连续的主机名)
    # MINIO_VOLUMES="http://minio-0{1...4}.internal.com:$MINIO_API_PORT$MINIO_DATA"
    cat > /etc/default/minio << EOF
MINIO_VOLUMES="$1"
MINIO_OPTS="--console-address :$MINIO_CONSOLE_PORT"
MINIO_ROOT_USER="$MINIO_ACCOUNT"
MINIO_ROOT_PASSWORD="$MINIO_PASSWD"
EOF
    chmod 644 /etc/default/minio
}

generate_minio_config() {

    #########################################################
    # Minio要求使用连续的IP，或连续的主机名，
    # 如果IP地址不是连续的，则需要配置连续的主机名，并配置/etc/hosts
    #########################################################

    # 定义节点配置数组（IP与主机名映射）
    local minio_nodes=(
        "172.26.85.156 minio-01.internal.com"
        "172.26.85.157 minio-02.internal.com"
        "172.26.85.158 minio-03.internal.com"
        "172.26.85.159 minio-04.internal.com"
    )
    # 获取匹配的主机名
    matched_hostname=""
    # 获取当前服务器的主机IP
    current_ip=$(ip -o -4 addr show scope global | awk '{print $4}' | cut -d '/' -f1 | head -1)

    # current_ip=$(hostname -I | awk '{print $1}')

    for node in "${minio_nodes[@]}"; do
        hostip=$(awk '{print $1}' <<< "$node")
        hostname=$(awk '{print $2}' <<< "$node")
        if [ "$current_ip" = "$hostip" ]; then
            #matched_hostname=$(awk '{print $2}' <<< "$node")
            matched_hostname="$hostname"
            log_success "匹配到主机: $matched_hostname"
            break
        fi
    done

    # 设置主机名
    while :
    do
        read -p "是否要按照IP与主机名映射设置主机名 ( Y/y 表示设置主机名, MINIO_VOLUMES采用主机名, N/n 表示不设置主机名, MINIO_VOLUMES采用IP): " -r option
        case "${option}" in
            Y|y)
                log_info "用户选择按照IP与主机名映射设置主机名"
                if [[ -n "$matched_hostname" ]]; then
                    hostnamectl set-hostname "$matched_hostname"
                else
                    log_error "当前IP($current_ip)未匹配任何节点配置，请检查网络设置"
                fi

                # 配置/etc/hosts
                for node in "${minio_nodes[@]}"; do
                    if ! grep -q "$node" /etc/hosts; then
                        echo "$node" | tee -a /etc/hosts >/dev/null
                    fi
                done

                local node_urls=()
                for node in "${minio_nodes[@]}"; do
                    hostname=$(awk '{print $2}' <<< "$node")
                    node_urls+=("http://${hostname}:${MINIO_API_PORT}${MINIO_DATA}")
                done

                generate_minio_volumes "${node_urls[*]}"

                break
            ;;
            N|n)
                log_info "用户选择不配置主机名称，将使用IP配置MINIO_VOLUMES"
                local node_urls=()
                for node in "${minio_nodes[@]}"; do
                    hostip=$(awk '{print $1}' <<< "$node")
                    node_urls+=("http://${hostip}:${MINIO_API_PORT}${MINIO_DATA}")
                done

                generate_minio_volumes "${node_urls[*]}"

                break
            ;;
            *)
                log_warn "无效输入，请输入 Y(y) 或 N(n)"
            ;;
        esac
    done

}

generate_minio_service () {

    log_info "开始配置MinIO服务"
cat > /usr/lib/systemd/system/minio.service << EOF
[Unit]
Description=MinIO
Documentation=https://min.io/docs/minio/linux/index.html
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/bin/minio

[Service]
WorkingDirectory=/usr/local

User=$MINIO_USER
Group=$MINIO_USER
ProtectProc=invisible

EnvironmentFile=-/etc/default/minio
ExecStartPre=/bin/bash -c "if [ -z \"\${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi"
ExecStart=/usr/local/bin/minio server \$MINIO_OPTS \$MINIO_VOLUMES

Restart=always

LimitNOFILE=65536

TasksMax=infinity

TimeoutStopSec=infinity
SendSIGKILL=no

[Install]
WantedBy=multi-user.target
EOF

    systemctl daemon-reload
    log_info "启动MinIO服务"
    systemctl enable --now minio.service
    systemctl status minio.service
}

main() {
    log_info "开始安装MinIO集群"
    generate_init_env
    generate_minio_config
    generate_minio_service
    log_success "MinIO集群安装完成"
}

main